package com.surfacing.rcp.server.controller;

import java.util.ArrayList;
import java.util.List;


import org.apache.log4j.Logger;

import com.surfacing.rcp.core.net.internal.MyClient;
import com.surfacing.rcp.model.codec.AnforderungDecoder;
import com.surfacing.rcp.model.codec.AnforderungEncoder;
import com.surfacing.rcp.model.codec.DienststelleDecoder;
import com.surfacing.rcp.model.codec.DienststelleEncoder;
import com.surfacing.rcp.model.codec.DoctorDecoder;
import com.surfacing.rcp.model.codec.DoctorEncoder;
import com.surfacing.rcp.model.codec.EinsatzartDecoder;
import com.surfacing.rcp.model.codec.EinsatzartEncoder;
import com.surfacing.rcp.model.codec.JournalDecoder;
import com.surfacing.rcp.model.codec.JournalEncoder;
import com.surfacing.rcp.model.codec.LoginDecoder;
import com.surfacing.rcp.model.codec.LoginEncoder;
import com.surfacing.rcp.model.codec.LogoutDecoder;
import com.surfacing.rcp.model.codec.LogoutEncoder;
import com.surfacing.rcp.model.codec.NacaDecoder;
import com.surfacing.rcp.model.codec.NacaEncoder;
import com.surfacing.rcp.model.codec.NfsDecoder;
import com.surfacing.rcp.model.codec.NfsEncoder;
import com.surfacing.rcp.model.codec.ProtokollNrDecoder;
import com.surfacing.rcp.model.codec.ProtokollNrEncoder;
import com.surfacing.rcp.model.codec.StatAnforderungDecoder;
import com.surfacing.rcp.model.codec.StatAnforderungEncoder;
import com.surfacing.rcp.model.codec.StatDienststelleDecoder;
import com.surfacing.rcp.model.codec.StatDienststelleEncoder;
import com.surfacing.rcp.model.codec.StatDoctorDecoder;
import com.surfacing.rcp.model.codec.StatDoctorEncoder;
import com.surfacing.rcp.model.codec.StatEinsatzartDecoder;
import com.surfacing.rcp.model.codec.StatEinsatzartEncoder;
import com.surfacing.rcp.model.codec.StatNacaDecoder;
import com.surfacing.rcp.model.codec.StatNacaEncoder;
import com.surfacing.rcp.model.codec.StatNfsDecoder;
import com.surfacing.rcp.model.codec.StatNfsEncoder;
import com.surfacing.rcp.model.codec.StatTransportzielDecoder;
import com.surfacing.rcp.model.codec.StatTransportzielEncoder;
import com.surfacing.rcp.model.codec.SystemMessageDecoder;
import com.surfacing.rcp.model.codec.SystemMessageEncoder;
import com.surfacing.rcp.model.codec.TransportzielDecoder;
import com.surfacing.rcp.model.codec.TransportzielEncoder;
import com.surfacing.rcp.model.codec.UserDecoder;
import com.surfacing.rcp.model.codec.UserEncoder;
import com.surfacing.rcp.model.common.AbstractMessage;
import com.surfacing.rcp.model.common.IModelActions;
import com.surfacing.rcp.model.entity.Anforderung;
import com.surfacing.rcp.model.entity.Dienststelle;
import com.surfacing.rcp.model.entity.Doctor;
import com.surfacing.rcp.model.entity.Einsatzart;
import com.surfacing.rcp.model.entity.Journal;
import com.surfacing.rcp.model.entity.Login;
import com.surfacing.rcp.model.entity.Logout;
import com.surfacing.rcp.model.entity.Naca;
import com.surfacing.rcp.model.entity.Nfs;
import com.surfacing.rcp.model.entity.ProtokollNr;
import com.surfacing.rcp.model.entity.StatAnforderung;
import com.surfacing.rcp.model.entity.StatDienststelle;
import com.surfacing.rcp.model.entity.StatDoctor;
import com.surfacing.rcp.model.entity.StatEinsatzart;
import com.surfacing.rcp.model.entity.StatNaca;
import com.surfacing.rcp.model.entity.StatNfs;
import com.surfacing.rcp.model.entity.StatTransportziel;
import com.surfacing.rcp.model.entity.SystemMessage;
import com.surfacing.rcp.model.entity.Transportziel;
import com.surfacing.rcp.model.entity.User;
import com.surfacing.rcp.model.factory.ProtocolCodecFactory;
import com.surfacing.rcp.model.factory.ServerListenerFactory;
import com.surfacing.rcp.model.factory.XMLFactory;
import com.surfacing.rcp.server.listener.AnforderungListener;
import com.surfacing.rcp.server.listener.AuthenticationListener;
import com.surfacing.rcp.server.listener.DienststelleListener;
import com.surfacing.rcp.server.listener.DoctorListener;
import com.surfacing.rcp.server.listener.EinsatzartListener;
import com.surfacing.rcp.server.listener.JournalListener;
import com.surfacing.rcp.server.listener.NacaListener;
import com.surfacing.rcp.server.listener.NfsListener;
import com.surfacing.rcp.server.listener.ProtokollNrListener;
import com.surfacing.rcp.server.listener.StatAnforderungListener;
import com.surfacing.rcp.server.listener.StatDienststelleListener;
import com.surfacing.rcp.server.listener.StatDoctorListener;
import com.surfacing.rcp.server.listener.StatEinsatzartListener;
import com.surfacing.rcp.server.listener.StatNacaListener;
import com.surfacing.rcp.server.listener.StatNfsListener;
import com.surfacing.rcp.server.listener.StatTransportzielListener;
import com.surfacing.rcp.server.listener.TransportzielListener;
import com.surfacing.rcp.server.listener.UserListener;

public class ServerController {
	// the shared instance
	private static ServerController serverController;

	// this pool contains all client connections form the application
	private List<ClientSession> connClientPool;

	// the logger
	private static Logger logger = Logger.getLogger(ServerController.class);

	/**
	 * Default private class constructor.<br>
	 * Setting up the client pools and registers the listener and codec classes.
	 */
	private ServerController() {
		// init the pools
		connClientPool = new ArrayList<ClientSession>();
		// register the encoders and decoders
		registerEncoderAndDecoder();
		registerModelListeners();
	}

	/**
	 * Returns the shared instance
	 * 
	 * @return the shared instance
	 */
	public static ServerController getDefault() {
		// create a new instance or reuse
		if (serverController == null)
			serverController = new ServerController();
		return serverController;
	}

	/**
	 * Invoked when a new client is connected to the server.<br>
	 * A new session will be created, the connection must first be authenticated
	 * to interact with the server.
	 * 
	 * @param client
	 *            the new conneted client
	 */
	public void clientConnected(MyClient client) {
		logger.info("Creating new session for client: "
				+ client.getSocket().getInetAddress());
		ClientSession session = new ClientSession(client);
		connClientPool.add(session);
	}

	/**
	 * Invoked when a client was disconnected throught abnormal programm
	 * termination or a network fault.<br>
	 * If the client was authenticated the other authenticated clients will be
	 * informed with a <code>SystemMessage</code>
	 * 
	 * @param client
	 *            the disconnected client
	 */
	public void clientDisconnected(MyClient client) {
		// get the user session
		ClientSession session = getSession(client);
		logger.info("Disconnect detected ( " + session + " ) ");
		// close the connection if we have one
		if (session.getConnection() != null) {
			session.getConnection().removeAllNetListeners();
			session.getConnection().requestStop();
			// close the socket when it is open
			if (session.getConnection().getSocket() != null)
				session.getConnection().getSocket().cleanup();
		}
		// check the pool and remove the session
		if (connClientPool.contains(session)) {
			connClientPool.remove(session);
			// brodcast the message to all other authenticated clients
			SystemMessage system = new SystemMessage("Client "
					+ session.getUsername() + " disconnected",
					SystemMessage.TYPE_INFO);
			brodcastMessage("system", SystemMessage.ID, IModelActions.SYSTEM,
					system);
		}
	}

	/**
	 * Returns the session object for this client connection
	 * 
	 * @param connection
	 *            the connection to get the session from
	 * @return the session object or null if no session is available
	 */
	public ClientSession getSession(MyClient connection) {
		for (ClientSession session : connClientPool) {
			if (session.getConnection() == connection)
				return session;
		}
		return null;
	}

	/**
	 * Encodes the list of message into xml and brodcasts the message all
	 * authenticated clients.
	 * 
	 * @param userId
	 *            the identification of the user.
	 * @param contentType
	 *            the type of the message content
	 * @param queryString
	 *            the type of the query
	 * @param objectList
	 *            a list of objects to send
	 */
	public synchronized void brodcastMessage(String userId, String contentType,
			String queryString, List<AbstractMessage> objectList) {
		// set up the factory
		XMLFactory factory = new XMLFactory();
		factory.setupEncodeFactory(userId, contentType, queryString);
		String message = factory.encode(objectList);
		// loop over the client pool and send the message
		for (ClientSession session : connClientPool) {
			// Send the message to all authenticated clients, except the web
			// clients
			if (session.isAuthenticated() & !session.isWebClient()) {
				MyClient client = session.getConnection();
				client.sendMessage(message);
				logger.debug("REPLY to " + session.getUsername() + " -> "
						+ userId + " : " + contentType + "->" + queryString);
			}
		}
	}

	/**
	 * Encodes the message into xml and brodcasts the message all authenticated
	 * clients.
	 * 
	 * @param userId
	 *            the identification of the user.
	 * @param contentType
	 *            the type of the message content
	 * @param queryString
	 *            the type of the query
	 * @param object
	 *            the message to send
	 */
	public synchronized void brodcastMessage(String userId, String contentType,
			String queryString, AbstractMessage object) {
		// create list
		ArrayList<AbstractMessage> list = new ArrayList<AbstractMessage>();
		list.add(object);
		// delegate
		brodcastMessage(userId, contentType, queryString, list);
	}

	/**
	 * Encodes the message into xml and sends the message to the client.
	 * 
	 * @param session
	 *            the session to send the message to
	 * @param contentType
	 *            the type of the message content
	 * @param queryString
	 *            the type of the query
	 * @param objectList
	 *            a list of objects to send
	 */
	public synchronized void sendMessage(ClientSession session,
			String contentType, String queryString,
			List<AbstractMessage> objectList) {
		MyClient connection = session.getConnection();
		String userId = session.getUsername();
		// When we have no user id then set it to system
		if (userId == null)
			userId = "system";
		// set up the factory
		XMLFactory factory = new XMLFactory();
		factory.setupEncodeFactory(userId, contentType, queryString);
		String xml = factory.encode(objectList);
		// encode and send
		connection.sendMessage(xml);
		if (logger.isDebugEnabled()) {
			logger.debug("Sending reply " + userId + " : " + contentType + "->"
					+ queryString);
			logger.debug("XML: " + xml.toString());
		}
	}

	/**
	 * Encodes the message into xml and sends the message to the client.
	 * 
	 * @param session
	 *            the session to send the message to
	 * @param contentType
	 *            the type of the message content
	 * @param queryString
	 *            the type of the query
	 * @param object
	 *            the message to send
	 */
	public synchronized void sendMessage(ClientSession session,
			String contentType, String queryString, AbstractMessage object) {
		// create list
		ArrayList<AbstractMessage> list = new ArrayList<AbstractMessage>();
		list.add(object);
		// delegate
		sendMessage(session, contentType, queryString, list);
	}

	/**
	 * Convenience method to registers the encoders and decoders.
	 */
	private void registerEncoderAndDecoder() {
		// register the needed model types with the decoders and encoders
		ProtocolCodecFactory protFactory = ProtocolCodecFactory.getDefault();
		protFactory.registerDecoder(Journal.ID, new JournalDecoder());
		protFactory.registerEncoder(Journal.ID, new JournalEncoder());
		protFactory.registerDecoder(Anforderung.ID, new AnforderungDecoder());
		protFactory.registerEncoder(Anforderung.ID, new AnforderungEncoder());
		protFactory.registerDecoder(Dienststelle.ID, new DienststelleDecoder());
		protFactory.registerEncoder(Dienststelle.ID, new DienststelleEncoder());
		protFactory.registerDecoder(Doctor.ID, new DoctorDecoder());
		protFactory.registerEncoder(Doctor.ID, new DoctorEncoder());
		protFactory.registerDecoder(Einsatzart.ID, new EinsatzartDecoder());
		protFactory.registerEncoder(Einsatzart.ID, new EinsatzartEncoder());
		protFactory.registerDecoder(Naca.ID, new NacaDecoder());
		protFactory.registerEncoder(Naca.ID, new NacaEncoder());
		protFactory.registerDecoder(Nfs.ID, new NfsDecoder());
		protFactory.registerEncoder(Nfs.ID, new NfsEncoder());
		protFactory.registerDecoder(User.ID, new UserDecoder());
		protFactory.registerEncoder(User.ID, new UserEncoder());
		protFactory.registerDecoder(Transportziel.ID,
				new TransportzielDecoder());
		protFactory.registerEncoder(Transportziel.ID,
				new TransportzielEncoder());
		// system events
		protFactory.registerDecoder(Login.ID, new LoginDecoder());
		protFactory.registerEncoder(Login.ID, new LoginEncoder());
		protFactory.registerDecoder(Logout.ID, new LogoutDecoder());
		protFactory.registerEncoder(Logout.ID, new LogoutEncoder());
		protFactory.registerDecoder(SystemMessage.ID,
				new SystemMessageDecoder());
		protFactory.registerEncoder(SystemMessage.ID,
				new SystemMessageEncoder());
		protFactory.registerDecoder(StatNfs.ID, new StatNfsDecoder());
		protFactory.registerEncoder(StatNfs.ID, new StatNfsEncoder());
		protFactory.registerDecoder(StatDoctor.ID, new StatDoctorDecoder());
		protFactory.registerEncoder(StatDoctor.ID, new StatDoctorEncoder());
		protFactory.registerDecoder(StatEinsatzart.ID,
				new StatEinsatzartDecoder());
		protFactory.registerEncoder(StatEinsatzart.ID,
				new StatEinsatzartEncoder());
		protFactory.registerDecoder(StatDienststelle.ID,
				new StatDienststelleDecoder());
		protFactory.registerEncoder(StatDienststelle.ID,
				new StatDienststelleEncoder());
		protFactory.registerDecoder(StatTransportziel.ID,
				new StatTransportzielDecoder());
		protFactory.registerEncoder(StatTransportziel.ID,
				new StatTransportzielEncoder());
		protFactory.registerDecoder(StatAnforderung.ID,
				new StatAnforderungDecoder());
		protFactory.registerEncoder(StatAnforderung.ID,
				new StatAnforderungEncoder());
		protFactory.registerDecoder(StatNaca.ID, new StatNacaDecoder());
		protFactory.registerEncoder(StatNaca.ID, new StatNacaEncoder());
		protFactory.registerDecoder(ProtokollNr.ID, new ProtokollNrDecoder());
		protFactory.registerEncoder(ProtokollNr.ID, new ProtokollNrEncoder());
	}

	/**
	 * Convinience method to registers the ui model listeners to get updates
	 * from the network layer.
	 */
	private void registerModelListeners() {
		ServerListenerFactory factory = ServerListenerFactory.getInstance();
		// register the listeners
		factory.registerModelListener(Journal.ID, new JournalListener());
		factory.registerModelListener(StatNfs.ID, new StatNfsListener());
		factory.registerModelListener(StatNaca.ID, new StatNacaListener());
		factory.registerModelListener(StatAnforderung.ID,
				new StatAnforderungListener());
		factory.registerModelListener(StatTransportziel.ID,
				new StatTransportzielListener());
		factory.registerModelListener(StatDienststelle.ID,
				new StatDienststelleListener());
		factory.registerModelListener(StatEinsatzart.ID,
				new StatEinsatzartListener());
		factory.registerModelListener(StatDoctor.ID, new StatDoctorListener());
		factory.registerModelListener(Login.ID, new AuthenticationListener());
		factory.registerModelListener(Logout.ID, new AuthenticationListener());
		factory.registerModelListener(Nfs.ID, new NfsListener());
		factory.registerModelListener(Anforderung.ID, new AnforderungListener());
		factory.registerModelListener(User.ID, new UserListener());
		factory.registerModelListener(Dienststelle.ID,
				new DienststelleListener());
		factory.registerModelListener(Doctor.ID, new DoctorListener());
		factory.registerModelListener(Einsatzart.ID, new EinsatzartListener());
		factory.registerModelListener(Naca.ID, new NacaListener());
		factory.registerModelListener(Transportziel.ID,
				new TransportzielListener());
		factory.registerModelListener(ProtokollNr.ID,
				new ProtokollNrListener());
	}
}
